import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


# 开始建立线性回归模型
class linear_regression():
    def fitness(self, Date_X_input, Date_Y, learning_rate=0.5, lamda=0.03):
        sample_num, property_num = Date_X_input.shape  # np.shape读取矩阵的长度，样本个数，样本属性个数
        Date_X = np.c_[Date_X_input, np.ones(sample_num)]  # 添加一列常数项
        self.theta = np.zeros([property_num + 1, 1])  # 初始化theta参数为零
        Max_count = int(1e8)  # 最大迭代次数
        last_better = 0  # 上一次较好误差的迭代次数
        last_Jerr = int(1e8)  # 上一次较好学习误差的误差函数值
        threshold_value = 1e-8  # 定义误差阈值
        threshold_count = 10  # 连续迭代次数后退出
        for step in range(0, Max_count):
            predict = Date_X.dot(self.theta)  # 预测
            J_theta = np.sum((predict - Date_Y) ** 2) / (2 * sample_num)  # 损失函数，使用np.sum避免警告
            self.theta -= learning_rate * (
                        lamda * self.theta + (Date_X.T.dot(predict - Date_Y)) / sample_num)  # 更新theta

            if J_theta < last_Jerr - threshold_value:  # 检测损失函数变化，满足条件提前结束迭代
                last_Jerr = J_theta
                last_better = step
            elif step - last_better > threshold_count:
                break

            if step % 50 == 0:  # 定期打印
                print(f"step {step}: {J_theta:.6f}")

    def predicted(self, X_input):
        sample_num = X_input.shape[0]  # 获取样本个数
        X = np.c_[X_input, np.ones(sample_num)]  # 添加常数项
        predict = X.dot(self.theta)
        return predict


def property_label(pd_data):
    row_num = pd_data.shape[0]
    column_num = len(pd_data.iloc[0, 0].split())  # 行数，列数
    X = np.empty([row_num, column_num - 1])
    Y = np.empty([row_num, 1])  # 初始化空数组
    for i in range(0, row_num):
        row_array = pd_data.iloc[i, 0].split()  # np.split 切分
        X[i] = np.array(row_array[0:-1])  # X = 所有列，除了最后一列
        Y[i] = np.array(row_array[-1])  # Y = 最后一列
    return X, Y


def standardization(X_input):  # 特征标准化
    Maxx = X_input.max(axis=0)
    Minx = X_input.min(axis=0)
    X = (X_input - Minx) / (Maxx - Minx)  # 对数据进行归一化处理
    return X, Maxx, Minx


if __name__ == "__main__":
    data = pd.read_csv("housing-data.csv", header=None)  # 读取数据
    Date_X, Date_Y = property_label(data)  # 对训练集进行X，Y分离
    Standard_DateX, Maxx, Minx = standardization(Date_X)  # 对X进行归一化处理

    model = linear_regression()
    model.fitness(Standard_DateX, Date_Y)

    Date_predict = model.predicted(Standard_DateX)
    Date_predict_error = np.sum((Date_predict - Date_Y) ** 2) / (2 * Standard_DateX.shape[0])  # 计算误差
    print(f"Test error is {Date_predict_error:.2f}")
    print(f"Theta: {model.theta}")

    # 绘图
    t = np.arange(len(Date_predict))
    plt.figure(facecolor='white')  # 设置背景颜色
    plt.plot(t, Date_Y, '#00AAAA', lw=1, label=u'actual value')  # 绘制真实价格
    plt.plot(t, Date_predict, '#FF5555', lw=1.6, label=u'estimated value')  # 绘制预测价格
    plt.legend(loc='upper right')
    plt.title(u'Boston house price', fontsize=20)
    plt.xlabel(u'case id', fontsize=12)
    plt.ylabel(u'house price', fontsize=12)
    plt.grid()  # 添加网格线
    plt.show()
